package edu.vcu.csl.ess;

import java.util.ArrayList;
import java.util.Comparator;
import java.io.*;

public class RawModel {
	public static class Constant {
		public String name, value, comment, error;
	}

	public static class Species  {
		public String name, population, comment, error;
	}

	public static class Reaction {
		public String name, equation, rate, comment, error;
		public ArrayList<ReactionElement> reactants     = new ArrayList<ReactionElement>();
		public ArrayList<ReactionElement> products      = new ArrayList<ReactionElement>();
		public ArrayList<ReactionElement> delta         = new ArrayList<ReactionElement>();
		public ArrayList<Reaction> affectedReactionList = new ArrayList<Reaction>();
		public ArrayList<Trigger>  affectedTriggerList  = new ArrayList<Trigger>();
	}

	public static class Event    {
		public String name, time, action, comment, error;
		public ArrayList<Reaction> affectedReactionList = new ArrayList<Reaction>();
		public ArrayList<Trigger>  affectedTriggerList  = new ArrayList<Trigger>();
	}

	public static class Periodic {
		public String name, start, period, end, action, comment, error;
		public ArrayList<Reaction> affectedReactionList = new ArrayList<Reaction>();
		public ArrayList<Trigger>  affectedTriggerList  = new ArrayList<Trigger>();
	}

	public static class Trigger  {
		public String name, condition, action, comment, error;
		public ArrayList<Reaction> affectedReactionList = new ArrayList<Reaction>();
		public ArrayList<Trigger>  affectedTriggerList  = new ArrayList<Trigger>();
	}

	public static class ReactionElement {
		public int coefficient; 
		public String species;
	};

	public static class EventSort implements Comparator<Event>{
		public int compare (Event o1, Event o2){
			if (Double.parseDouble(o1.time) <
					Double.parseDouble(o2.time)){
				return -1;
			}
			else if (Double.parseDouble(o1.time) >
			Double.parseDouble(o2.time)){
				return 1;
			}
			else{
				return 0;
			}
		}
	}

	public ArrayList<Constant>   constants  = new ArrayList<Constant>();
	public ArrayList<Species>    species    = new ArrayList<Species>();
	public ArrayList<Reaction>   reactions  = new ArrayList<Reaction>();
	public ArrayList<Event>      events     = new ArrayList<Event>();
	public ArrayList<Periodic>   periodics  = new ArrayList<Periodic>();
	public ArrayList<Trigger>    triggers   = new ArrayList<Trigger>();
	public double                start      = 0.0;
	public double                end        = 10.0;
	public double                interval   = 1.0;
	public int                   seed       = 1;
	public String                filename   = "out.txt";
	public ArrayList<String>     errors     = new ArrayList<String>();

	public RawModel() {    		

	}

	public void write(File file) throws IOException  {
		PrintStream out = new PrintStream(new FileOutputStream(file));

		out.println("-- RawModel FILE v1.1 --");

		out.println(constants.size());
		for(int i=0; i<constants.size(); i++) {
			Constant x = constants.get(i);
			out.println(x.name.replace('\n','\t'));
			out.println(x.value.replace('\n','\t'));
			out.println(x.comment.replace('\n','\t'));
		}

		out.println(species.size());
		for(int i=0; i<species.size(); i++) {
			Species x = species.get(i);
			out.println(x.name.replace('\n','\t'));
			out.println(x.population.replace('\n','\t'));
			out.println(x.comment.replace('\n','\t'));
		}

		out.println(reactions.size());
		for(int i=0; i<reactions.size(); i++) {
			Reaction x = reactions.get(i);
			out.println(x.name.replace('\n','\t'));
			out.println(x.equation.replace('\n','\t'));
			out.println(x.rate.replace('\n','\t'));
			out.println(x.comment.replace('\n','\t'));
		}

		out.println(events.size());
		for(int i=0; i<events.size(); i++) {
			Event x = events.get(i);
			out.println(x.name.replace('\n','\t'));
			out.println(x.time.replace('\n','\t'));
			out.println(x.action.replace('\n','\t'));
			out.println(x.comment.replace('\n','\t'));
		}

		out.println(periodics.size());
		for(int i=0; i<periodics.size(); i++) {
			Periodic x = periodics.get(i);
			out.println(x.name.replace('\n','\t'));
			out.println(x.start.replace('\n','\t'));
			out.println(x.period.replace('\n','\t'));
			out.println(x.end.replace('\n','\t'));
			out.println(x.action.replace('\n','\t'));
			out.println(x.comment.replace('\n','\t'));
		}

		out.println(triggers.size());
		for(int i=0; i<triggers.size(); i++) {
			Trigger x = triggers.get(i);
			out.println(x.name.replace('\n','\t'));
			out.println(x.condition.replace('\n','\t'));
			out.println(x.action.replace('\n','\t'));
			out.println(x.comment.replace('\n','\t'));
		}

		out.println(start);
		out.println(end);
		out.println(interval);
		out.println(seed);
		out.println(filename);

		out.close();
	}

	public static RawModel read(File file) throws IOException {
		BufferedReader in = new BufferedReader(new FileReader(file));
		RawModel model = new RawModel();
		int count;

		if (!in.readLine().equals("-- RawModel FILE v1.1 --"))
			throw new IOException();

		count = Integer.parseInt(in.readLine());
		for(int i=0; i<count; i++) {
			model.addConstant(in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'));
		}

		count = Integer.parseInt(in.readLine());
		for(int i=0; i<count; i++) {
			model.addSpecies(in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'));
		}

		count = Integer.parseInt(in.readLine());
		for(int i=0; i<count; i++) {
			model.addReaction(in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'));
		}

		count = Integer.parseInt(in.readLine());
		for(int i=0; i<count; i++) {
			model.addEvent(in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'));
		}

		count = Integer.parseInt(in.readLine());
		for(int i=0; i<count; i++) {
			model.addPeriodic(in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'));
		}

		count = Integer.parseInt(in.readLine());
		for(int i=0; i<count; i++) {
			model.addTrigger(in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'),
					in.readLine().replace('\t','\n'));
		}

		model.setStartTime(Double.parseDouble(in.readLine()));
		model.setEndTime(Double.parseDouble(in.readLine()));
		model.setPrintInterval(Double.parseDouble(in.readLine()));
		model.setSeed(Integer.parseInt(in.readLine()));
		model.setOutputFilename(in.readLine());

		return model;
	}


	public void addConstant(String name,
			String value,
			String comment) {
		Constant p = new Constant();
		p.name = name;
		p.value = value;
		p.comment = comment;
		p.error = null;
		constants.add(p);
	}

	public void addSpecies(String name,
			String population,
			String comment) {
		Species s = new Species();
		s.name = name;
		s.population = population;
		s.comment = comment;
		s.error = null;
		species.add(s);
	}

	public void addReaction(String name,
			String equation,
			String rate,
			String comment) {
		Reaction r = new Reaction();
		r.name = name;
		r.equation = equation;
		r.rate = rate;
		r.comment = comment;
		r.error = null;
		reactions.add(r);
	}

	public void addEvent(String name,
			String time,
			String action,
			String comment) {
		Event e = new Event();
		e.name = name;
		e.time = time;
		e.action = action;
		e.comment = comment;
		e.error = null;
		events.add(e);
	}

	public void addPeriodic(String name,
			String start,
			String period,
			String end,
			String action,
			String comment) {
		Periodic p = new Periodic();
		p.name = name;
		p.start = start;
		p.period = period;
		p.end = end;
		p.action = action;
		p.comment = comment;
		p.error = null;
		periodics.add(p);
	}

	public void addTrigger(String name,
			String condition,
			String action,
			String comment) {
		Trigger t = new Trigger();
		t.name = name;
		t.condition = condition;
		t.action = action;
		t.comment = comment;
		t.error = null;
		triggers.add(t);
	}

	public void setStartTime(double start) {
		this.start = start;
	}

	public void setEndTime(double end) {
		this.end = end;
	}

	public void setPrintInterval(double interval) {
		this.interval = interval;
	}

	public void setSeed(int seed) {
		this.seed = seed;
	}

	public void setOutputFilename(String filename) {
		this.filename = filename;
	}

	public boolean run() {
		return RawModelCheck.isValid(this);
	}

	public void printErrors() {
		for(Constant x : constants) {
			if (x.error != null) {
				System.out.println("Error: ");
				System.out.println("Constant " + x.name);
				System.out.println(x.error);
				System.out.println();
			}
		}

		for(Species x : species) {
			if (x.error != null) {
				System.out.println("Error: ");
				System.out.println("Species " + x.name);
				System.out.println(x.error);
				System.out.println();
			}
		}

		for(Reaction x : reactions) {
			if (x.error != null) {
				System.out.println("Error: ");
				System.out.println("Reaction " + x.name);
				System.out.println(x.error);
				System.out.println();
			}
		}

		for(Event x : events) {
			if (x.error != null) {
				System.out.println("Error: ");
				System.out.println("Event " + x.name);
				System.out.println(x.error);
				System.out.println();
			}
		}

		for(Periodic x : periodics) {
			if (x.error != null) {
				System.out.println("Error: ");
				System.out.println("Periodic Event " + x.name);
				System.out.println(x.error);
				System.out.println();
			}
		}

		for(Trigger x : triggers) {
			if (x.error != null) {
				System.out.println("Error: ");
				System.out.println("Trigger " + x.name);
				System.out.println(x.error);
				System.out.println();
			}
		}

		for(String x : errors) {
			System.out.println("Error: ");
			System.out.println(x);
			System.out.println();
		}
	}

	public static void main(String args[]) throws IOException {
		if (args.length != 1) {
			System.out.println("usage: java RawModel <filename>");
			System.exit(1);
		}

		RawModel model = RawModel.read(new File(args[0]));
		if (model.run() == false) {
			model.printErrors();	    
		} else {
			System.out.println("Construction complete...");
			ModelCodeGen.generateOutputFile(model);
		}
	}
}